/*
 * Created on 2008-3-21
 *
 * TODO
 */
package jacky.lanlan.song.test.common;

import static org.junit.Assert.*;

import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 包含了单元测试经常需要用到的检测方法，以及一些方便测试的支持部件。 比如看代码是否执行到某个地方的标记 <code>getThere</code>，以及计数器
 * <code>counter</code>等，这些东西统称为Pin，当使用过后，可以调用 <code>resetPins()</code> 来重置这些部件。
 * 
 * @author Jacky.Song
 */
public class UnitUtils {
	
	private static final Map<String, Class<?>> primitiveTypeNameMap = new HashMap<String, Class<?>>(16);
	
	private static final Map<Class<?>, Object> primitiveDefault;
	
	static {
		Map<Class<?>, Object> map = new HashMap<Class<?>, Object>(12);
		map.put(byte.class, (byte) 0);
		map.put(short.class, (short) 0);
		map.put(int.class, 0);
		map.put(long.class, 0l);
		map.put(float.class, 0.0f);
		map.put(double.class, 0.0d);
		map.put(boolean.class, false);
		map.put(char.class, (char) 0);
		primitiveDefault = Collections.unmodifiableMap(map);
		
		Set<Class<?>> primitiveTypeNames = new HashSet<Class<?>>(16);
		Class<?>[] primi = { boolean.class, byte.class, char.class, double.class, float.class, int.class, long.class,
				short.class };
		primitiveTypeNames.addAll(Arrays.asList(primi));
		primitiveTypeNames.addAll(Arrays.asList(new Class<?>[] { boolean[].class, byte[].class, char[].class,
				double[].class, float[].class, int[].class, long[].class, short[].class }));
		for (Iterator<?> it = primitiveTypeNames.iterator(); it.hasNext();) {
			Class<?> primitiveClass = (Class<?>) it.next();
			primitiveTypeNameMap.put(primitiveClass.getName(), primitiveClass);
		}
	}
	
	private static final Calendar CALENDAR = Calendar.getInstance();
	
	private final static Lock LOCK = new ReentrantLock();
	
	/**
	 * 标识代码运行到某处
	 */
	private AtomicBoolean getThere = new AtomicBoolean();
	
	/**
	 * 计数器
	 */
	private AtomicInteger counter = new AtomicInteger();
	
	/**
	 * 重置内置的各种测试支持部件。
	 */
	public void resetPins() {
		getThere.set(false);
		counter.set(0);
	}
	
	/**
	 * 重置给定的 Field 到初始默认值。
	 */
	public static void resetField(Object... fields) {
		for (Object field : fields) {
			field = defautValue(field.getClass());
		}
	}
	
	/**
	 * 重置给定对象的所有 Field 到初始默认值。
	 */
	public static void resetFields(final Object testcase) {
		try {
			for (Field field : testcase.getClass().getDeclaredFields()) {
				if (field.getDeclaringClass() == testcase.getClass()) {
					field.setAccessible(true);
					Object value = defautValue(field.getClass());
					field.set(testcase, value);
				}
			}
		}
		catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
	
	/**
	 * 返回给定类型的Java规范默认值。
	 */
	public static Object defautValue(Class<?> type) {
		if (primitiveTypeNameMap.containsKey(type.getSimpleName())) { return primitiveDefault.get(type); }
		return null;
	}
	
	/**
	 * 重置测试类的所有Pin及其声明字段。
	 */
	public void resetAll(Object testcase) {
		this.resetPins();
		resetFields(testcase);
	}
	
	/**
	 * 测试时间。
	 */
	public static void checkDate(Date time, int year, int month, int date) {
		LOCK.lock();
		try {
			CALENDAR.setTime(time);
			assertEquals(year, CALENDAR.get(Calendar.YEAR));
			assertEquals(month - 1, CALENDAR.get(Calendar.MONTH));
			assertEquals(date, CALENDAR.get(Calendar.DATE));
		}
		finally {
			LOCK.unlock();
		}
	}
	
	public AtomicInteger getCounter() {
		return this.counter;
	}
	
	/**
	 * 计数器加1。
	 */
	public int increaseCounter() {
		return counter.incrementAndGet();
	}
	
	/**
	 * 计数器减1。
	 */
	public int decreaseCounter() {
		return counter.decrementAndGet();
	}
	
	/**
	 * 检查计数器。
	 * 
	 * @param expect
	 *          期望值
	 * @param msg
	 *          失败信息
	 */
	public void checkCounter(int expect, String msg) {
		assertEquals(msg, expect, counter.get());
	}
	
	/**
	 * 检查计数器。
	 * 
	 * @param expect
	 *          期望值
	 */
	public void checkCounter(int expect) {
		assertEquals(expect, counter.get());
	}
	
	/**
	 * 标记该位置。
	 */
	public void markHere() {
		this.getThere.compareAndSet(false, true);
	}
	
	/**
	 * 检测代码是否执行到标记位置。
	 * <p>
	 * 注意，这个方法和markHere()方法配套使用，每当调用一次markHere()，该方法才会成功一次，<i>重复调用 该方法被认为是错误的，会导致检测失败。</i>
	 */
	public void checkMark() {
		checkMark("代码没有运行到那里");
	}
	
	/**
	 * 检测代码是否执行到标记位置。
	 * <p>
	 * 注意，这个方法和markHere()方法配套使用，每当调用一次markHere()，该方法才会成功一次，<i>重复调用 该方法被认为是错误的，会导致检测失败。</i>
	 * 
	 * @param megWhenNotGetThere
	 *          如果没有执行到标记位置的提示信息
	 */
	public void checkMark(String megWhenNotGetThere) {
		try {
			assertTrue(megWhenNotGetThere, this.getThere.get());
		}
		finally {
			this.getThere.set(false); // 归位
		}
	}
	
	/**
	 * 生成测试对象。委托给 {@code RandomDataGenerator}。
	 * 
	 * @param <T>
	 *          目标类型参数
	 * @param type
	 *          目标类
	 * @return
	 */
	public static <T> T geneRandomData(Class<T>... types) {
		return (T) RandomDataGenerator.geneRandomValue(types);
	}
	
}
